بناء تطبيق جهات الاتصال باستخدام Xamarin – الجزء الثاني
في الجزء الأول من سلسلة مقالات بناء تطبيق جهات الاتصال باستخدام Xamarin.Forms، تم التطرق إلى المفاهيم الأساسية المرتبطة بتهيئة بيئة التطوير، إعداد المشروع، وربط واجهات المستخدم مع قاعدة بيانات SQLite. في هذا الجزء الثاني، سيتم التعمق في تطوير الوظائف العملية لتطبيق جهات الاتصال، بما يشمل إضافة ميزات متقدمة مثل التحديث والحذف، تحسينات واجهة المستخدم باستخدام XAML، تكامل مع واجهة برمجية API (اختياري)، واستخدام نمط التصميم MVVM بشكل فعّال لتنظيم الكود وضمان قابليته للصيانة والتوسع في المستقبل.
بنية المشروع والتنظيم باستخدام MVVM
قبل الشروع في تنفيذ الوظائف، من المهم تنظيم هيكلية المشروع وفقًا لنمط Model-View-ViewModel (MVVM). هذا النمط يفصل بين منطق العرض (View)، منطق العمل (ViewModel)، والنماذج (Model).
بنية المجلدات المقترحة:
-
Models: يحتوي على تعريف نموذج جهة الاتصال.
-
Views: يحتوي على صفحات XAML الخاصة بالعرض مثل صفحة عرض جميع جهات الاتصال، صفحة الإضافة، وصفحة التعديل.
-
ViewModels: يحتوي على ملفات ViewModel التي تتوسط بين الواجهة والموديل.
-
Services: يحتوي على الخدمات مثل SQLiteService.
إنشاء نموذج جهة الاتصال
في مجلد Models، يتم تعريف كلاس Contact الذي يمثل البيانات الأساسية:
csharpusing SQLite;
namespace ContactApp.Models
{
public class Contact
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[MaxLength(100)]
public string Name { get; set; }
[MaxLength(100)]
public string Email { get; set; }
[MaxLength(15)]
public string PhoneNumber { get; set; }
public string Address { get; set; }
public string Notes { get; set; }
}
}
خدمة SQLite
في مجلد Services، يتم تنفيذ SQLiteService لتوفير وظائف CRUD:
csharpusing SQLite;
using ContactApp.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ContactApp.Services
{
public class SQLiteService
{
private readonly SQLiteAsyncConnection _database;
public SQLiteService(string dbPath)
{
_database = new SQLiteAsyncConnection(dbPath);
_database.CreateTableAsync().Wait();
}
public Task<List<Contact>> GetContactsAsync()
{
return _database.Table().ToListAsync();
}
public Task<Contact> GetContactAsync(int id)
{
return _database.Table().Where(c => c.Id == id).FirstOrDefaultAsync();
}
public Task<int> SaveContactAsync(Contact contact)
{
if (contact.Id != 0)
return _database.UpdateAsync(contact);
else
return _database.InsertAsync(contact);
}
public Task<int> DeleteContactAsync(Contact contact)
{
return _database.DeleteAsync(contact);
}
}
}
ربط قاعدة البيانات في App.xaml.cs
csharppublic static SQLiteService Database { get; private set; }
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new ContactListPage());
string dbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "contacts.db3");
Database = new SQLiteService(dbPath);
}
صفحات العرض (Views)
صفحة عرض جميع جهات الاتصال – ContactListPage.xaml
xml<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
Title="جهات الاتصال">
<StackLayout Padding="10">
<ListView x:Name="contactsListView"
ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Name}" Detail="{Binding PhoneNumber}" />
DataTemplate>
ListView.ItemTemplate>
ListView>
<Button Text="إضافة جهة اتصال" Clicked="OnAddClicked"/>
StackLayout>
ContentPage>
صفحة إضافة/تعديل جهة اتصال – ContactDetailPage.xaml
xml<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
Title="تفاصيل جهة الاتصال">
<StackLayout Padding="20" Spacing="10">
<Entry Placeholder="الاسم" Text="{Binding Name}" />
<Entry Placeholder="رقم الهاتف" Text="{Binding PhoneNumber}" Keyboard="Telephone" />
<Entry Placeholder="البريد الإلكتروني" Text="{Binding Email}" Keyboard="Email" />
<Editor Placeholder="العنوان" Text="{Binding Address}" HeightRequest="100"/>
<Editor Placeholder="ملاحظات" Text="{Binding Notes}" HeightRequest="100"/>
<Button Text="حفظ" Clicked="OnSaveClicked"/>
<Button Text="حذف" Clicked="OnDeleteClicked" TextColor="Red" IsVisible="{Binding IsEditing}"/>
StackLayout>
ContentPage>
ViewModels
ContactListViewModel.cs
csharpusing System.Collections.ObjectModel;
using System.Threading.Tasks;
using Xamarin.Forms;
using ContactApp.Models;
namespace ContactApp.ViewModels
{
public class ContactListViewModel : BindableObject
{
public ObservableCollection Contacts { get; private set; }
public ContactListViewModel()
{
Contacts = new ObservableCollection();
LoadContacts();
}
public async void LoadContacts()
{
Contacts.Clear();
var contacts = await App.Database.GetContactsAsync();
foreach (var contact in contacts)
Contacts.Add(contact);
}
}
}
ContactDetailViewModel.cs
csharpusing System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
using ContactApp.Models;
namespace ContactApp.ViewModels
{
public class ContactDetailViewModel : INotifyPropertyChanged
{
private Contact _contact;
public event PropertyChangedEventHandler PropertyChanged;
public Contact Contact
{
get => _contact;
set
{
_contact = value;
OnPropertyChanged(nameof(Contact));
}
}
public bool IsEditing => Contact?.Id > 0;
public ContactDetailViewModel(Contact contact = null)
{
Contact = contact ?? new Contact();
}
public async void SaveContact()
{
await App.Database.SaveContactAsync(Contact);
await Application.Current.MainPage.Navigation.PopAsync();
}
public async void DeleteContact()
{
await App.Database.DeleteContactAsync(Contact);
await Application.Current.MainPage.Navigation.PopAsync();
}
protected void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
تحسين تجربة المستخدم
دعم الترجمة وتعدد اللغات
استخدام RESX يتيح توفير ترجمات للنصوص الثابتة، مما يسهل تعريب التطبيق بشكل احترافي.
دعم الصور والرموز
إضافة صورة شخصية اختيارية لكل جهة اتصال يمكن أن يعزز من جمالية التطبيق وواقعيته. يمكن استخدام Image وMediaPlugin لالتقاط صورة أو تحميلها من الجهاز.
الجدول التالي يلخص الوظائف الأساسية ومكان تنفيذها في البنية المعمارية
| الوظيفة | View | ViewModel | Model / Service |
|---|---|---|---|
| عرض قائمة جهات الاتصال | ContactListPage | ContactListViewModel | SQLiteService |
| إضافة جهة اتصال | ContactDetailPage | ContactDetailViewModel | Contact, SQLiteService |
| تعديل جهة اتصال | ContactDetailPage | ContactDetailViewModel | Contact, SQLiteService |
| حذف جهة اتصال | ContactDetailPage | ContactDetailViewModel | Contact, SQLiteService |
| ربط البيانات بالواجهة | عبر XAML | عبر BindingContext | – |
| الحفظ في قاعدة البيانات | – | ContactDetailViewModel | SQLiteService |
تكامل مع واجهة API مستقبلًا
على الرغم من أن التطبيق في نسخته الحالية يعمل محليًا فقط عبر قاعدة بيانات SQLite، فإن تصميمه باستخدام MVVM يسهل إدخال طبقة API مستقبلًا لتخزين ومزامنة البيانات مع خوادم خارجية. يمكن استبدال SQLiteService بـ IApiService دون التأثير على واجهات المستخدم.
استخدام Dependency Injection
توفير الخدمات عبر Dependency Injection يُعد خطوة مهمة لتسهيل اختبار المكونات. يمكن استخدام مكتبة مثل Microsoft.Extensions.DependencyInjection أو TinyIoC.
اختبار التطبيق
اختبار واجهة المستخدم
-
اختبار الاستجابة للأزرار والتنقل بين الصفحات.
-
التأكد من ظهور البيانات بشكل صحيح عند التحميل.
اختبار البيانات
-
إدخال بيانات وهمية والتأكد من تخزينها وعرضها.
-
حذف جهة اتصال والتأكد من إزالتها من القائمة.
نشر التطبيق على الأجهزة الفعلية
بعد الانتهاء من التطوير، يمكن نشر التطبيق على أنظمة:
-
Android: عبر Android SDK وملف .APK.
-
iOS: عبر XCode وApple Developer Account.
يُوصى بإجراء اختبار على الأجهزة الفعلية لمعالجة الاختلافات بين المحاكيات والهواتف الحقيقية.
خاتمة فنية
يُمثل هذا الجزء الثاني من سلسلة بناء تطبيق جهات الاتصال باستخدام Xamarin خطوة متقدمة نحو إنتاج تطبيق حقيقي يلبي احتياجات المستخدم النهائي. بالاستفادة من نمط MVVM وتنظيم الكود بشكل هرمي، يصبح من الممكن توسيع التطبيق ليشمل مزايا إضافية مثل مزامنة البيانات، الدعم السحابي، والتكامل مع خدمات الجهات الخارجية. يعتمد نجاح تطوير تطبيقات Xamarin على التخطيط المسبق وتنظيم الطبقات بشكل يضمن القابلية للتعديل والاختبار، مما يشكل حجر الأساس لأي تطبيق مهني يعمل على منصات متعددة.
المراجع:
-
Charles Petzold. Creating Mobile Apps with Xamarin.Forms. Microsoft Press, 2016.
-
Xamarin Documentation. https://learn.microsoft.com/en-us/xamarin

